package de.fru;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

import de.fru.ByteStream.EofException;

public class Instruction {

	public static class Constant extends Instruction {
		private Variable assignment = DelegatedVariable.STACK;
		private Object object;

		public Constant(Object object) {
			this.object = object;
		}

		public String toString() {
			return assignment + " = " + object;
		}
	}

	public static class Noop extends Instruction {
		public String toString() {
			return "noop";
		}
	}

	public static class Call extends Instruction {
		private Variable assignment;
		private Object function;
		private ArrayList<Variable> parameter;

		public String toString() {
			String out = assignment + " = " + function + "( ";
			for (Variable l : parameter) {
				out += l + " ";
			}
			return out + ")";
		}
		
		/*public Call setOnLocal(int local){
			while(this.local.size() <= local){
				this.local.add(null);
			}
			this.local.set(local, assignment);
			return this;
		}*/
		
		public Call popStackParameter(){
			parameter.add(DelegatedVariable.STACK);
			return this;
		}
		
		public Call addLocalParamter(int l){
			parameter.add(new DelegatedVariable(l));
			return this;
		}
		
		public Call addParameter(Variable variable){
			parameter.add(variable);
			return this;
		}
		
		public Call(Variable assignment, Object function){
			this.assignment = assignment;
			this.function = function;
			this.parameter = new ArrayList<Variable>();
		}
		
		public Call(Object function){
			this(new Variable(),function);
		}
	}
	
	public static class Conditional extends Instruction {
		protected static final int END = Integer.MAX_VALUE;
		protected static final String GOTO = "goto";
		protected static final String FROM = "from";
		
		private Variable conditional;
		private int jump;
		private Object move;
		private Stack<Variable> stack;
		private ArrayList<Variable> local;
		
		public Conditional(Object move,Variable conditional, int jump){
			this.conditional = conditional;
			this.jump = jump;
			this.move = move;
		}
		
		public String toString(){
			String jump = (this.jump == Conditional.END ? "END" : ""+this.jump);
			return move+" "+jump + " when "+conditional; 
		}
	}

	/*protected Stack<Variable> stack = null;
	protected ArrayList<Variable> local = null;
	
	protected static Stack<Variable> lastStack = null;
	protected static ArrayList<Variable> lastLocal = null;

	@SuppressWarnings("unchecked")
	public Instruction() {
		if (instructions == null || instructions.size() == 0) {
			stack = new Stack<Variable>();
			local = new ArrayList<Variable>();
			local.add(new Variable("this"));
		} else {
			Instruction last = instructions.get(instructions.size() - 1);
			this.stack = ((Stack<Variable>) (lastStack==null?last.stack:lastStack).clone());
			this.local = ((ArrayList<Variable>) (lastLocal==null?last.local:lastLocal).clone());
		}
	}*/

	public static class Variable {
		private static int counter = 100;
		private final String name;
		
		private boolean delegated = false;

		public Variable() {
			name = "local." + counter++;
		}

		public Variable(String name) {
			this.name = name;
		}
		
		Variable setDelegated(){
			delegated = true;
			return this;
		}
		
		boolean isDelegated(){
			return delegated;
		}

		public String toString() {
			return name;
		}
	}
	
	public static class DelegatedVariable extends Variable{
		public static final Variable STACK = new Variable("system.stack").setDelegated();
		
		int location;
		public DelegatedVariable(int location){
			super("system.local");
			this.location = location;
			this.setDelegated();
		}
		
		public String toString(){
			return super.toString()+location;
		}
		
		public static Variable undelegateRead(Stack<Variable> stack, ArrayList<Variable> local, Variable v){
			if(v == STACK)return stack.pop();
			else if(v instanceof DelegatedVariable){
				try{ // remove
					return local.get(((DelegatedVariable)v).location);
				}catch(Exception e){
					return v;
				}
				
			}else{
				return v;
			}
		}
		
		public static Variable undelegateWrite(Stack<Variable> stack, ArrayList<Variable> local, Variable v){
			if(v == STACK){
				stack.push(v = new Variable());
				return v;
			}else if(v instanceof DelegatedVariable){
				int i = ((DelegatedVariable)v).location;
				while(i >= local.size())local.add(null);
				local.set(i, v = new Variable());
				return v;
			}else{
				return v;
			}
		}
	}

	private static ArrayList<Instruction> instructions;
	
	@SuppressWarnings("unchecked")
	public static ArrayList<Instruction> parseInstructions(ByteStream byteStream){
		int start = byteStream.getByteCount();
		final HashMap<Integer, Integer> bytePositionToInstruction = new HashMap<Integer, Integer>();
		instructions = new ArrayList<Instruction>();
	    try{
	      while(true){
	    	 int pos = byteStream.getByteCount();
	    	 bytePositionToInstruction.put(pos-start, instructions.size());
	    	 Instruction.parseNext(byteStream, pos);
	      }
	    }catch(EofException e){
	      //Expected Exception when codeblock ends
	    }
	    
	    for(Instruction i : instructions){
	    	if(i instanceof Conditional){
				Conditional g = (Conditional)i;
				if(g.jump != Conditional.END){
					g.jump = bytePositionToInstruction.get(g.jump);
				}	
			}
	    }
	    
	    //Add Conditional: FROM
	    for(int e = 0; e < instructions.size(); e++){
	    	if(instructions.get(e) instanceof Conditional){
	    		Conditional c = (Conditional)instructions.get(e);
	    		if(c.move == Conditional.GOTO && c.jump != Conditional.END){
	    			Conditional from = new Conditional(Conditional.FROM, null, e);
		    		ArrayList<Instruction> add = new ArrayList<Instruction>();
					add.add(from);
					addInstructions(instructions, c.jump, add);
					if(c.jump < e){
		    			c.jump--;
		    			from.jump++;
		    			e++;
		    		}else if(c.jump > e){
		    			c.jump--;
		    		}
	    		}
	    	}
	    }
	    
	    
	    //Resolve Delegated Variables
	    Stack<Variable> stack = new Stack<Variable>();
		ArrayList<Variable> local = new ArrayList<Variable>();
		for(int i = 0; i < instructions.size(); i++){
			Instruction ins = instructions.get(i);
	    	if(ins instanceof Conditional){
	    		Conditional current = (Conditional)ins;
	    		current.conditional = DelegatedVariable.undelegateRead(stack, local, current.conditional);
	    		current.stack = (Stack<Variable>) stack.clone();
	    		current.local = (ArrayList<Variable>) local.clone();
	    		if(current.jump < i && instructions.get(current.jump) instanceof Conditional){
	    			Conditional back = (Conditional)instructions.get(current.jump);
	    			stack = (Stack<Variable>) back.stack.clone();
	    			local = (ArrayList<Variable>) back.local.clone();
	    		}
	    	}else if(ins instanceof Call){
	    		Call current = (Call)ins;
	    		ArrayList<Variable> list = new ArrayList<Variable>();
	    		for(Variable v : current.parameter){
	    			list.add(DelegatedVariable.undelegateRead(stack, local, v));
	    		}
	    		current.parameter = list;
	    		current.assignment = DelegatedVariable.undelegateWrite(stack, local, current.assignment);
	    	}else if(ins instanceof Constant){
	    		Constant current = (Constant)ins;
	    		current.assignment = DelegatedVariable.undelegateWrite(stack, local, current.assignment);
	    	}
	    }
		
		//Add Assignments 
		for(int i = 0; i < instructions.size(); i++){
			Instruction ins = instructions.get(i);
	    	if(ins instanceof Conditional){
	    		Conditional current = (Conditional)ins;
	    		if(current.jump < i && current.jump != Conditional.END){
	    			if(instructions.get(current.jump) instanceof Conditional){
	    				
	    			}
	    			ArrayList<Instruction> list = new ArrayList<Instruction>();
	    			Conditional back = (Conditional) instructions.get(current.jump);
	    			while(!back.stack.isEmpty() || !current.stack.isEmpty()){
	    				list.add(new Call(back.stack.pop(),"=").addParameter(current.stack.pop()));
	    			}
	    			for(int j = 0; j < current.local.size(); j++){
	    				list.add(new Call(back.local.get(j),"=").addParameter(current.local.get(j)));
	    			}
	    			Iterator<Instruction> iterator = list.iterator();
	    			while(iterator.hasNext()){
	    				Call call = (Call)iterator.next();
	    				if(call.assignment == call.parameter.get(0)){
	    					iterator.remove();
	    				}
	    			}
	    			addInstructions(instructions, i, list);
	    			i += list.size();
	    		}
	    	}
		}
	    
	    
	    int j = 0;
	    for(Instruction i : instructions){
	    	System.out.println(j++ + ": " +  i);
	    }
	    
	    return instructions;
	}
	
	private static void addInstructions(ArrayList<Instruction> instructions, int position, ArrayList<Instruction> add){
		for(Instruction i : instructions){
			if(i instanceof Conditional){
				Conditional c = (Conditional)i;
				if(c.jump >= position && c.jump != Conditional.END) c.jump += add.size();
			}
		}
		instructions.addAll(position, add);
	}

	private static void parseNext(ByteStream byteStream, int bytePosition) {
		int b = byteStream.findNext();
		if (0x02 <= b && b <= 0x08) { // integer -1,0,1,2,3,4,5
			instructions.add(new Constant(b - 0x03));
		} else if(0x3B <= b && b <= 0x3E) {
			instructions.add(new Call(new DelegatedVariable(b - 0x3B),"=").popStackParameter());
		} else if(0x10 == b){
			instructions.add( new Constant(byteStream.findNext()));
		} else if(0x1A <= b && b <= 0x1D){
			instructions.add( new Call(DelegatedVariable.STACK,"=").addLocalParamter(b-0x1A));
		} else if(0x99 <= b && b <= 0x9E) {
			String f = new String[]{"==","!=","<",">=",">","<="}[b-0x99];
			Constant zero =  new Constant(0);
			Call comparison = new Call(f).popStackParameter().addParameter(zero.assignment);
			Conditional jump = new Conditional(Conditional.GOTO,comparison.assignment, bytePosition + (short)byteStream.findShort());
			instructions.add(zero);
			instructions.add(comparison);
			instructions.add(jump);
		} else if(0x9F <= b && b <= 0xA4) {
			String f = new String[]{"==","!=","<",">=",">","<="}[b-0x9F];
			Call comparison = new Call(f).popStackParameter().popStackParameter();
			Conditional jump = new Conditional(Conditional.GOTO,comparison.assignment, bytePosition + (short)byteStream.findShort());
			instructions.add(comparison);
			instructions.add(jump);
		} else if(0x84 == b) {
			int l = byteStream.findNext();
			Constant c = new Constant(byteStream.findNext());
			Call add = new Call(new DelegatedVariable(l),"+").addLocalParamter(l).addParameter(c.assignment);
			instructions.add(c);
			instructions.add(add);
		} else if(0xA7 == b) {	
			Constant c = new Constant(true);
			Conditional g = new Conditional(Conditional.GOTO,c.assignment, bytePosition + (short)byteStream.findShort());
			instructions.add(c);
			instructions.add(g);
		} else if(0xAC == b){
			Call c = new Call(new Variable("return"),"=").popStackParameter();
			Constant t = new Constant(true);
			Conditional g = new Conditional(Conditional.GOTO,t.assignment, Conditional.END);
			instructions.add(c);
			instructions.add(t);
			instructions.add(g);
		} else if(0xB1 == b){
			Constant t = new Constant(true);
			Conditional g = new Conditional(Conditional.GOTO,t.assignment, Conditional.END);
			instructions.add(t);
			instructions.add(g);			
		} else if(0x2A <= b && b <= 0x2D){
			instructions.add( new Call(DelegatedVariable.STACK,"=").addLocalParamter(b-0x2A));
		} else {
			System.out.println(ByteStream.format(b));
			instructions.add( new Noop());
			//throw byteStream.new BytecodeException("Unexpected bytecode");
		}
	}

}
